﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using CBTM.Core;
using CBTM.Processor;
using System.Linq;

namespace CBTM.Model
{
    public class Model
    {
        #region Variable

        private float threshold = (float)0.01;
        private int maxloop = 20;

        private List<string> StopList;
        private Dictionary<string, List<string[]>> TTable;
        private Dictionary<string, string> SCTable;

        private Dictionary<string, float> P_VEk;
        private Dictionary<string, float> preP_VEk;

        // tần số unigram và bigram ứng với g
        private Dictionary<string, Dictionary<string, float>> Fu;
        private Dictionary<string, Dictionary<string, float>> Fb;

        private Dictionary<string, float> Pu_UG;
        private Dictionary<string, float> Pb_BG;

        private Dictionary<string, float> P1_VEk;
        private Dictionary<string, float> P1_Ek = new Dictionary<string, float>();

        #endregion

        #region Property

        public float Threshold
        {
            get { return threshold; }
            set { threshold = value; }
        }

        public int MaximumLoops
        {
            get { return maxloop; }
            set { maxloop = value; }
        }

        #endregion

        #region Class-based Translation Model

        private void Initialization(string bst_path, Dictionary<string, List<string[]>> t_table,
            out Dictionary<string, float> p_vek_list)
        {
            p_vek_list = new Dictionary<string, float>();

            // tạo mới
            if (!File.Exists(bst_path))
            {
                XElement bst_root = new XElement("Root");
                foreach (var t in t_table)
                {
                    string e_k = t.Key;
                    XElement bst_node = new XElement("Node", new XAttribute("Sense", e_k));
                    // xác suất dịch khởi tạo P = 1/n
                    float initP = (float)1 / (float)t.Value.Count;
                    foreach (string[] translation_fragments in t.Value)
                    {
                        string translation = (string.Join("_", translation_fragments)).ToLower();
                        p_vek_list.Add(translation + ":" + e_k, initP);
                        bst_node.Add(new XElement("Translation", translation + ":" + initP));
                    }
                    bst_root.Add(bst_node);
                }
                bst_root.Save(bst_path);
                Console.WriteLine("BSTTable1.xml does not exist.\nApplication will create one and start running.");
            }
            // load tập tin hiện tại để tiếp tục tính toán
            else
            {
                XElement bst_root = XElement.Load(bst_path);
                foreach (XElement bst_node in bst_root.Elements())
                {
                    string e_k = bst_node.FirstAttribute.Value;
                    foreach (var p_node in bst_node.Elements())
                    {
                        string[] bst_pair = p_node.Value.Split(':');
                        //string p = pair[1];
                        //p = p.Substring(p.LastIndexOf(';') + 1);
                        float p = float.Parse(bst_pair[1]);
                        //float p = float.Parse(p);
                        p_vek_list.Add(bst_pair[0] + ':' + e_k, p);
                    }
                }
                Console.WriteLine("BSTTable1.xml found.\nContinue running from last save of work.");
            }
        }

        private void Maximization(Dictionary<string, string> sc_table, List<string> stoplist,
            Dictionary<string, Dictionary<string, float>> fu_list, Dictionary<string, Dictionary<string, float>> fb_list,
            Dictionary<string, float> p_vek_list,
            out Dictionary<string, float> pu_ug_list, out Dictionary<string, float> pb_bg_list)
        {
            // lưu tử số của công thức
            Dictionary<string, Dictionary<string, float>> pu_numerators = new Dictionary<string, Dictionary<string, float>>();
            Dictionary<string, Dictionary<string, float>> pb_numerators = new Dictionary<string, Dictionary<string, float>>();
            // lưu mẫu số
            Dictionary<string, float> pu_denominators = new Dictionary<string, float>();
            Dictionary<string, float> pb_denominators = new Dictionary<string, float>();

            // tính tử số
            foreach (var bst in p_vek_list)
            {
                string[] bst_array = bst.Key.Split(':');
                string e_k = bst_array[1] + ":" + bst_array[2];
                string g = Misc.GetClassCode(bst_array[2], sc_table);
                //if (g == string.Empty) continue;
                if (g == null) continue;
                string[] unigrams = bst_array[0].Split('_');
                // float val = (float)1 / unigrams.Length;
                float pu = bst.Value / unigrams.Length;
                float pb = bst.Value / (unigrams.Length - 1);

                // tính tử số
                Dictionary<string, float> pu_numerator;
                Dictionary<string, float> pb_numerator;
                if (pu_numerators.TryGetValue(g, out pu_numerator))
                {
                    pb_numerator = pb_numerators[g];
                    int index = 0;
                    string pre_u = string.Empty;
                    foreach (string u in unigrams)
                    {
                        float current_p = 0;
                        if (!stoplist.Contains(u))
                            if (pu_numerator.TryGetValue(u, out current_p))
                            {
                                //float tmp = u_p * Declarator.Pu[g][u];
                                pu_numerator[u] += (pu * fu_list[g][u]);
                            }
                            else
                            {
                                //float tmp = u_p * Declarator.Pu[g][u];
                                pu_numerator.Add(u, pu * fu_list[g][u]);
                            }
                        if (index > 0)
                        {
                            string b = pre_u + '_' + u;
                            if (pb_numerator.TryGetValue(b, out current_p))
                            {
                                //float tmp = pb * Fb[g][b];
                                pb_numerator[b] += (pb * fb_list[g][b]);
                            }
                            else
                            {
                                pb_numerator.Add(b, pb * fb_list[g][b]);
                                float tmp = pb * fb_list[g][b];
                            }
                        }
                        pre_u = u;
                        ++index;
                    }
                    pu_numerators[g] = pu_numerator;
                    pb_numerators[g] = pb_numerator;
                }
                else
                {
                    pu_numerator = new Dictionary<string, float>();
                    pb_numerator = new Dictionary<string, float>();
                    int index = 0;
                    string pre_u = string.Empty;
                    foreach (string u in unigrams)
                    {
                        float p = 0;
                        if (!StopList.Contains(u))
                            if (pu_numerator.TryGetValue(u, out p))
                                pu_numerator[u] += (pu * fu_list[g][u]);
                            else
                                pu_numerator.Add(u, pu * fu_list[g][u]);
                        if (index > 0)
                        {
                            string b = pre_u + '_' + u;
                            if (pb_numerator.TryGetValue(b, out p))
                                pb_numerator[b] += (pb * fb_list[g][b]);
                            else
                                pb_numerator.Add(b, pb * fb_list[g][b]);
                        }
                        pre_u = u;
                        ++index;
                    }
                    pu_numerators.Add(g, pu_numerator);
                    pb_numerators.Add(g, pb_numerator);
                }
            }

            // tính mẫu số
            foreach (var numerator in pu_numerators)
            {
                pu_denominators.Add(numerator.Key, 0);
                foreach (var pu in numerator.Value)
                    pu_denominators[numerator.Key] += pu.Value;
                //if (numerator.Key == "N07301336")
                //{
                //    StreamWriter sw = new StreamWriter("Resources/u.txt");
                //    foreach (var pu in numerator.Value)
                //        sw.WriteLine(pu.Key + ":" + pu.Value);
                //    sw.Close();
                //}
            }
            foreach (var numerator in pb_numerators)
            {
                pb_denominators.Add(numerator.Key, 0);
                foreach (var pb in numerator.Value)
                    pb_denominators[numerator.Key] += pb.Value;
                //if (numerator.Key == "N07301336")
                //{
                //    StreamWriter sw = new StreamWriter("Resources/b.txt");
                //    foreach (var pb in numerator.Value)
                //        sw.WriteLine(pb.Key + ":" + pb.Value);
                //    sw.Close();
                //}
            }

            // tính P(u|g) và P(b|g)
            pu_ug_list = new Dictionary<string, float>();
            pb_bg_list = new Dictionary<string, float>();
            foreach (var numerator in pu_numerators)
                foreach (var pu_ug in numerator.Value)
                    pu_ug_list.Add(pu_ug.Key + ":" + numerator.Key, pu_ug.Value / pu_denominators[numerator.Key]);
            foreach (var numerator in pb_numerators)
                foreach (var pb_bg in numerator.Value)
                    pb_bg_list.Add(pb_bg.Key + ":" + numerator.Key, pb_bg.Value / pb_denominators[numerator.Key]);
        }

        private void Expectation(Dictionary<string, float> p_vek_list,
            Dictionary<string, float> pu_ug_list, Dictionary<string, float> pb_bg_list,
            out Dictionary<string, float> p1_vek_list, out Dictionary<string, float> p1_ek_list)
        {
            p1_ek_list = new Dictionary<string, float>();
            p1_vek_list = new Dictionary<string, float>();
            List<string> vek_list = new List<string>(p_vek_list.Keys);
            foreach (string vek in vek_list)
            {
                string[] vek_array = vek.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
                string[] unigrams = vek_array[0].Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
                string g = Misc.GetClassCode(vek_array[2], SCTable);
                //if (g == string.Empty) continue;
                if (g == null) continue;
                float pu = 0;
                float pb = 0;
                int idx = 0;
                string pre_u = string.Empty;
                foreach (string u in unigrams)
                {
                    float p;
                    if (pu_ug_list.TryGetValue(u + ':' + g, out p))
                        pu += p;
                    if (idx > 0)
                        pb += pb_bg_list[pre_u + '_' + u + ':' + g];
                    pre_u = u;
                    ++idx;
                }
                float p1_vek = 0;
                if (unigrams.Length > 1)
                    p1_vek = (pu / unigrams.Length + pb / (unigrams.Length - 1)) / 2;
                else
                    p1_vek = pu / unigrams.Length;
                p1_vek_list.Add(vek, p1_vek);

                string e_k = vek_array[1] + ':' + vek_array[2];
                float current_p1 = 0;
                if (p1_ek_list.TryGetValue(e_k, out current_p1))
                    p1_ek_list[e_k] += p1_vek;
                else
                    p1_ek_list.Add(e_k, p1_vek);
            }
        }

        private void Normalization(Dictionary<string, float> p1_vek, Dictionary<string, float> p1_ek,
            out Dictionary<string, float> p_vek)
        {
            //p_vek = p1_vek;
            p_vek = new Dictionary<string, float>();
            int p1_vek_count = p1_vek.Count;
            List<string> keys = new List<string>(p1_vek.Keys);
            foreach (string vek in keys)
            {
                string e_k = vek.Substring(vek.IndexOf(':') + 1);
                //p_vek[vek] /= p1_ek[e_k];
                p_vek.Add(vek, p1_vek[vek] / p1_ek[e_k]);
            }
            //for (int i = -1; ++i < p1_vek_count; )
            //{
            //    string vek = p_vek.ElementAt(i).Key;
            //    string e_k = vek.Substring(vek.IndexOf(':') + 1);
            //    p_vek[vek] /= p1_ek[e_k];
            //}
            //p1_ek.Clear();
        }

        public void Run()
        {
            int loop = 0;
            bool converge = false;

            StopList = Misc.LoadStopWords(Paths.SW_En);
            //FileEngine.Prepare(Paths.MRD_EVDictG_new);
            FileEngine.LoadTables(out SCTable, out TTable);

            Console.WriteLine("Initializing...");
            Initialization(Paths.BSTTable1, TTable, out preP_VEk);
            P_VEk = new Dictionary<string, float>(preP_VEk);
            Misc.GetFreqList(SCTable, StopList, P_VEk, out Fu, out Fb);

            while (!converge)
            {
                Console.WriteLine("Current loop: " + loop);

                Console.WriteLine("Maximizing...");
                Maximization(SCTable, StopList, Fu, Fb, P_VEk, out Pu_UG, out Pb_BG);

                Console.WriteLine("Expectating...");
                Expectation(P_VEk, Pu_UG, Pb_BG, out P1_VEk, out P1_Ek);
                P_VEk.Clear();
                Pu_UG.Clear();
                Pb_BG.Clear();

                Console.WriteLine("Normalizing...");
                Normalization(P1_VEk, P1_Ek, out P_VEk);
                P1_VEk.Clear();
                P1_Ek.Clear();

                Console.WriteLine("Reviewing...");
                preP_VEk = Misc.Review1(preP_VEk, P_VEk, Paths.BSTCheck, threshold, out converge);
                FileEngine.CreateBSTTable1(preP_VEk, Paths.BSTTable1);

                ++loop;
                if (maxloop > 0 && loop > maxloop)
                    Console.WriteLine("Reach maximum number of loops.");
                Console.WriteLine();
            }
        }

        #endregion
    }
}